package uk.ac.cam.cl.projectcharlie2010.clients.desktop.gui;

import java.awt.AWTEvent;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JFileChooser;

import uk.ac.cam.cl.projectcharlie2010.CharliesConstants;
import uk.ac.cam.cl.projectcharlie2010.clients.desktop.controller.CharlieProgramLogicException;
import uk.ac.cam.cl.projectcharlie2010.clients.desktop.controller.DesktopController;
import uk.ac.cam.cl.projectcharlie2010.clients.desktop.gui.gamesetup.GameSetupPanel;
import uk.ac.cam.cl.projectcharlie2010.game.GameController;
import uk.ac.cam.cl.projectcharlie2010.game.GameControllerResult;
import uk.ac.cam.cl.projectcharlie2010.game.player.Player;
import uk.ac.cam.cl.projectcharlie2010.game.player.PlayerEngine;
import uk.ac.cam.cl.projectcharlie2010.game.player.PlayerHuman;
import uk.ac.cam.cl.projectcharlie2010.game.player.physical.PlayerPhysical;

import ch.nostromo.lib.controller.NosResourcesException;
import ch.nostromo.lib.controller.NosSettingsException;
import ch.nostromo.lib.swing.NosDesktopFrame;
import ch.nostromo.lib.swing.NosDialog;
import ch.nostromo.lib.swing.NosMenu;
import ch.nostromo.lib.swing.NosMenuBar;
import ch.nostromo.lib.swing.NosMenuItem;
import ch.nostromo.lib.swing.NosPanel;
import ch.nostromo.tiffanys.clients.desktop.gui.about.AboutPanel;
import ch.nostromo.tiffanys.clients.desktop.gui.board.BoardFrame;
import ch.nostromo.tiffanys.clients.desktop.gui.engine.EngineFrame;
import ch.nostromo.tiffanys.clients.desktop.gui.history.HistoryFrame;
import ch.nostromo.tiffanys.game.board.Board;
import ch.nostromo.tiffanys.game.game.GameException;
import ch.nostromo.tiffanys.game.move.Move;
import ch.nostromo.tiffanys.game.move.MoveInput;
import ch.nostromo.tiffanys.game.player.PlayerException;

public class Desktop extends NosDesktopFrame {

  BoardFrame currentBoardFrame;
  EngineFrame currentEngineFrame;
  HistoryFrame currentHistoryFrame;
  GeneralStatusFrame currentGeneralStatusFrame;

  private static final long serialVersionUID = 1L;

  private static Logger logger = Logger.getLogger(Desktop.class.getName());

  NosMenuBar mainMenuBar = new NosMenuBar();

  NosMenu menuFile = new NosMenu();
  private NosMenuItem menuItemGameSettings;
  NosMenuItem menuItemFileNewGame = new NosMenuItem();
  NosMenuItem menuItemFileLoad = new NosMenuItem();
  NosMenuItem menuItemFileSave = new NosMenuItem();
  NosMenuItem menuItemFileQuit = new NosMenuItem();

  NosMenu menuGame = new NosMenu();
  NosMenuItem menuItemGameTakeBack = new NosMenuItem();

  NosMenu menuHelp = new NosMenu();
  NosMenuItem menuItemHelpAbout = new NosMenuItem();

  /**
   * Constructor for the Desktop object
   * @throws NosResourcesException 
   */
  public Desktop() throws NosResourcesException {
    super();
    logger.fine("Desktop() called");
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    initGUI();
    initNOS();

  }

  private void initGUI() {
    logger.fine("jbInit() called");

    this.setSize(new Dimension(800, 800));

    menuFile.setText("File");

    menuItemFileQuit.setText("Quit");
    menuItemFileQuit.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        quitInvoked(e);
      }
    });

    menuItemFileNewGame.setText("New Game");
    menuItemFileNewGame.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        newGameInvoked();
      }
    });

    menuItemFileLoad.setText("Load Game");
    menuItemFileLoad.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        loadGameInvoked();
      }
    });

    menuItemFileSave.setText("Save Game");
    menuItemFileSave.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        saveGameInvoked();
      }
    });

    menuGame.setText("Game");



    menuItemGameTakeBack.setText("Take move back");
    menuItemGameTakeBack.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        takeMoveBackInvoked();
      }

    });

    menuHelp.setText("Help");

    menuItemHelpAbout.setText("About");
    menuItemHelpAbout.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        aboutInvoked();
      }
    });    

    menuFile.add(menuItemFileNewGame);
    menuItemFileNewGame.setTextTranslationKey("Menu.File.NewGame");
    menuFile.addSeparator();
    //menuFile.add(menuItemFileLoad);
    menuItemFileLoad.setTextTranslationKey("Menu.File.LoadGame");
    //menuFile.add(menuItemFileSave);
    menuItemFileSave.setTextTranslationKey("Menu.File.SaveGame");
    //menuFile.addSeparator();
    menuFile.add(menuItemFileQuit);
    menuItemFileQuit.setTextTranslationKey("Menu.File.Quit");

    {
      menuItemGameSettings = new NosMenuItem();
      menuGame.add(menuItemGameSettings);
      menuItemGameSettings.setText("Settings");
      menuItemGameSettings.setTextTranslationKey("Menu.Game.Settings");
    }
    
    menuItemGameSettings.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        gameSettingsInvoked();
      }
    });

    
    //menuGame.addSeparator();
    
    //TODO: More change?
    //menuGame.add(menuItemGameTakeBack);
    menuItemGameTakeBack.setTextTranslationKey("Menu.Game.TakeBack");


    menuHelp.add(menuItemHelpAbout);
    menuItemHelpAbout.setTextTranslationKey("Menu.Help.About");

    mainMenuBar.add(menuFile);
    menuFile.setTextTranslationKey("Menu.File");
    mainMenuBar.add(menuGame);
    menuGame.setTextTranslationKey("Menu.Game");
    mainMenuBar.add(menuHelp);
    menuHelp.setTextTranslationKey("Menu.Help");

    this.setJMenuBar(mainMenuBar);
    
    this.setTextTranslate(false);
  }

  private void initNOS() throws NosResourcesException  {
    this.setTitle(DesktopController.getInstance().getTranslation("ApplicationTitle") + " " + uk.ac.cam.cl.projectcharlie2010.Charlies.CHARLIE_VERSION);
  }


  /**
   * Close the desktop & application
   * 
   * @param e
   *          Description of the Parameter
   */
  @Override
  public void closeDesktop(WindowEvent e) {
    logger.fine("closeDesktop() called.");
    super.closeDesktop(e);
    DesktopController.endApp();
  }

  /**
   * Quit application
   * 
   * @param e
   *          Description of the Parameter
   */
  void quitInvoked(ActionEvent e) {
    closeDesktop(null);
  }


  void takeMoveBackInvoked() {
    logger.fine("takeMoveBackInvoked() called.");
    
    try {
      DesktopController.getInstance().takeBackMove();
    } catch (GameException e) {
      logger.log(Level.SEVERE, "takeMoveBackInvoked() failed.", e);
    }
  }

  public void newGameInvoked() {
    logger.fine("newGameInvoked() called.");

    try {

      GameController gc = DesktopController.getInstance().generateDefaultGameController();
      boolean newGame = true;
      String title = DesktopController.getInstance().getTranslation("Setup.Title.NewGame");
      GameSetupPanel panel = new GameSetupPanel(gc, newGame);

      this.showPanelAsDialog(panel, title);

    } catch (NosResourcesException e) {
      logger.log(Level.SEVERE, "newGameInvoked() failed.", e);
    } catch (PlayerException e) {
      logger.log(Level.SEVERE, "newGameInvoked() failed.", e);
    } catch (CloneNotSupportedException e) {
      logger.log(Level.SEVERE, "newGameInvoked() failed.", e);
    } catch (NosSettingsException e) {
      logger.log(Level.SEVERE, "newGameInvoked() failed.", e);
    }
  }

  void gameSettingsInvoked() {
    logger.fine("newGameInvoked() called.");

    try {

      GameController gc = DesktopController.getInstance().getCurrentGameController();
      boolean newGame = false;
      String title = DesktopController.getInstance().getTranslation("Setup.Title.SetupGame");
      GameSetupPanel panel = new GameSetupPanel(gc, newGame);

      this.showPanelAsDialog(panel, title);

    } catch (NosResourcesException e) {
      logger.log(Level.SEVERE, "gameSettingsInvoked() failed.", e);
    } catch (PlayerException e) {
      logger.log(Level.SEVERE, "gameSettingsInvoked() failed.", e);
    } catch (CloneNotSupportedException e) {
      logger.log(Level.SEVERE, "gameSettingsInvoked() failed.", e);
    }
  }

  void loadGameInvoked() {
    logger.fine("loadGameInvoked() called.");
    File pgnFile = getFileToOpen("", "pgn", "PGN - Portable Game Notation");

    DesktopController.getInstance().loadGame(pgnFile);
  }

  void saveGameInvoked() {
    logger.fine("loadGameInvoked() called.");
    String filename = File.separator + "pgn";
    JFileChooser fc = new JFileChooser(new File(filename));
    fc.showSaveDialog(this);

    DesktopController.getInstance().saveGame(fc.getSelectedFile());
  }

  void aboutInvoked() {
    logger.fine("aboutInvoked() called");

    try {

      AboutPanel panel = new AboutPanel();
      String title = DesktopController.getInstance().getTranslation("About.Title");
      this.showPanelAsDialog(panel, title);

    } catch (NosResourcesException e) {
      logger.log(Level.SEVERE, "newGameInvoked() failed.", e);
    }    
    
  }
  
  public void showBoard() throws NosResourcesException {
    logger.fine("showBoard() called.");

    if (currentBoardFrame != null) {
      currentBoardFrame.dispose();
    }
    
    currentBoardFrame = new BoardFrame(this);
    currentBoardFrame.setResizable(false);
    synchronized (this) {
      getDesktop().add(currentBoardFrame, getDesktop().getComponentCount());
    }

    // centerFrame(currentBoardFrame);
    currentBoardFrame.setLocation(20, 20);
    currentBoardFrame.initNosFrame();
  }

  public void showEngineStats() {
    logger.fine("showEngineStats() called.");

    if (currentEngineFrame != null) {
      currentEngineFrame.dispose();
    }
    
    currentEngineFrame = new EngineFrame();
    currentEngineFrame.setResizable(true);
    synchronized (this) {
      getDesktop().add(currentEngineFrame, getDesktop().getComponentCount());
    }

    // centerFrame(currentBoardFrame);
    currentEngineFrame.setLocation(410, 20);
    
    try {
      DesktopController.getInstance().translateComponents(currentEngineFrame);
    } catch (NosResourcesException e) {
      logger.log(Level.SEVERE, "Resources Exception", e);
    }
    
    currentEngineFrame.initNosFrame();
  }

  public void showHistory() {
    logger.fine("showHistory() called.");

    if (currentHistoryFrame != null) {
      currentHistoryFrame.dispose();
    }
    
    currentHistoryFrame = new HistoryFrame();
    currentHistoryFrame.setResizable(true);
    synchronized (this) {
      getDesktop().add(currentHistoryFrame, getDesktop().getComponentCount());
    }

    // centerFrame(currentBoardFrame);
    currentHistoryFrame.setLocation(20, 420);
    currentHistoryFrame.initNosFrame();
  }
  
  public void showGeneralStatus() {
	    logger.fine("showGeneralStatus() called.");

	    if (currentGeneralStatusFrame != null) {
	    	currentGeneralStatusFrame.dispose();
	    }
	    
	    currentGeneralStatusFrame = new GeneralStatusFrame();
	    currentGeneralStatusFrame.setResizable(true);
	    synchronized (this) {
	      getDesktop().add(currentGeneralStatusFrame, getDesktop().getComponentCount());
	    }

	    // centerFrame(currentBoardFrame);
	    currentGeneralStatusFrame.setLocation(410, 420);
	    currentGeneralStatusFrame.initNosFrame();
	  }
  
  public void setGeneralStatusMessage(String msg) {
	  currentGeneralStatusFrame.setGeneralStatusMessage(msg);
  }

  // //////////////////////////////////////////////////////////////////////////////////////////
  // CHESS STUFF
  // //////////////////////////////////////////////////////////////////////////////////////////

  private void updateHistory() {
    currentHistoryFrame.setPGN(DesktopController.getInstance().getPGN());
  }

  public void announceGameEnd(String msg) {
    logger.fine("announceGameEnd() called.");
    updateHistory();
    this.showMessage(msg);

  }

  public void enterMove(MoveInput move) {
    logger.fine("enterMove() called.");
    DesktopController.getInstance().enterMove(move);
  }
  
  public boolean isPromotionMove(MoveInput move) {
    return DesktopController.getInstance().isPromotionMove(move);
  }
 
  public void engineMoveNow() {
    DesktopController.getInstance().engineMoveNow();
  }
  
  public void notifyComputeNextMoveFinished(GameControllerResult gameControllerResult) {
    logger.fine("notifyComputeNextMoveFinished() called.");

    // Update board
    Board currentBoard = DesktopController.getInstance().getCurrentBoard();
    Move lastMove = gameControllerResult.getSelectedMove();
    currentBoardFrame.getBoardPanel().notifyComputeNextMoveFinished(currentBoard, lastMove);

    // Update stats
    currentEngineFrame.setEngineStats(gameControllerResult);

    // Update history
    updateHistory();

   
  }

  public void notifyComputeNextMoveDepthFinished(GameControllerResult gameControllerResult) {
    logger.fine("notifyComputeNextMoveDepthFinished() called");
    currentEngineFrame.setEngineStats(gameControllerResult);
  }

  public void closeInterface() {
    logger.fine("closeInterface() called.");
    currentBoardFrame.getBoardPanel().closeInterface();
    currentEngineFrame.setComputing(true);
  }

  public void openInterface() {
	setGeneralStatusMessage(CharliesConstants.DO_GUI_MOVE);
    logger.fine("openInterface() called.");
    currentBoardFrame.getBoardPanel().openInterface();
    currentBoardFrame.setTitle(DesktopController.getInstance().getGameHeader());
    currentEngineFrame.setComputing(false);
  }

  public void enteredMoveAcceptedEngineTurn() {
    logger.fine("enteredMoveAcceptedEngineTurn() called.");
    currentBoardFrame.getBoardPanel().setBoard(DesktopController.getInstance().getCurrentBoard());
    currentBoardFrame.getBoardPanel().enteredMoveAccepted();

    // Update history
    updateHistory();
  }

  //CHANGED: Engine message updated - and method name and sig changed
  public void enteredMoveAcceptedNonEngineTurn(Player currentPlayer) {
    logger.fine("enteredMoveAcceptedNonEngineTurn() called.");
    currentBoardFrame.getBoardPanel().setBoard(DesktopController.getInstance().getCurrentBoard());
    currentBoardFrame.getBoardPanel().enteredMoveAccepted();

    // Update history
    updateHistory();

    // Engine stats
    //CHANGED: Engine status msg changed
    setEngineMessage(currentPlayer);
    
  }
  

  
//CHANGED: New method for flexible setting of engine message
//NOTE: Not sure if "PlayerEngine" case can occur
private void setEngineMessage(Player currentPlayer) {
	if (currentPlayer instanceof PlayerHuman) {
    	currentEngineFrame.setEngineMessage("Awaiting human move"); 
	} else if(currentPlayer instanceof PlayerPhysical) {
		currentEngineFrame.setEngineMessage("Awaiting physical move");
	} else if(currentPlayer instanceof PlayerEngine) {
		currentEngineFrame.setEngineMessage("Thinking...");
	} else {
		throw new CharlieProgramLogicException("Unexpected player type in call to setEngineMessage(Player p)");
	}
}

  public void enteredMoveRejected() {
    logger.fine("enteredMoveRejected() called.");
    currentBoardFrame.getBoardPanel().enteredMoveRejected();
  }

  public void newGameStarted() throws NosResourcesException {
    logger.fine("newGameStarted() called.");
    showEngineStats();
    showGeneralStatus();
    showHistory();
    updateHistory();

    showBoard();
    currentBoardFrame.getBoardPanel().setBoard(DesktopController.getInstance().getCurrentBoard());

  }

  public void gameLoaded() {
    logger.fine("gameLoaded() called.");

    updateHistory();

    //TODO test!
    setEngineMessage(DesktopController.getInstance().getCurrentGameController().getCurrentPlayer());

    currentBoardFrame.getBoardPanel().setBoard(DesktopController.getInstance().getCurrentBoard());

  }

  public void engineIsComputing() {
    logger.fine("engineIsComputing() called.");
    currentEngineFrame.setEngineMessage("Thinking ....");
    currentEngineFrame.setComputing(true);
  }

  public void moveTakenBack() {
    updateHistory();

    currentEngineFrame.setEngineMessage("Awaiting human move");  //TODO FIX! e.g. test current Player

    currentBoardFrame.getBoardPanel().setBoard(DesktopController.getInstance().getCurrentBoard());

  }

  public void showPanelAsDialog(NosPanel panel, String title) throws NosResourcesException {
    NosDialog dialog = new NosDialog(this, title, true, panel);
    DesktopController.getInstance().translateComponents(dialog);

    dialog.setResizable(false);
    dialog.setModal(true);
    
    
    int x = (this.getWidth() - dialog.getWidth()) / 2;
    int y = (this.getHeight() - dialog.getHeight()) / 2;
    dialog.setLocation(x, y);

    dialog.setVisible(true);
  }

public void setArmMovingStatusMessage() {
	currentGeneralStatusFrame.setArmMovingStatusMessage();
}

public void revertGeneralStatusMessage() {
	currentGeneralStatusFrame.revertGeneralStatusMessage();
}



}
